<?php


namespace App\Common\Services;

use App\Common\Models\OssFileModel;
use App\Exceptions\ApiException;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use OSS\Core\OssException;
use OSS\OssClient;

class OssFileService
{
    private $client;
    private $bucket;
    private $config;
    private $model;

    const UPLOAD_ALLOWS = [
        'image' => ['jpg', 'jpeg', 'gif', 'png'],
        'file' => ['webp', 'ico', '3gpp', 'ac3',
            'asf', 'au', 'csv', 'doc', 'dot', 'dtd', 'dwg', 'dxf', 'jp2', 'jpe','avif',
            'mp2', 'mp3', 'mp4', 'mpeg', 'mpg', 'mpp', 'ogg', 'pdf', 'pot', 'pps', 'ppt', 'pptx', 'rtf', 'svf',
            'wav', 'wma', 'flac', 'midi', 'ra', 'ape', 'aac', 'cda', 'mov',
            'avi', 'wmv', 'm4v', 'flv', 'f4v', 'rmvb', 'rm', '3gp',
            'tif','pjp','xbm','jxl','svgz','jfif','bmp','pjpeg', 'tiff', 'txt', 'wdb', 'wps', 'xhtml', 'xlc', 'xlm', 'xls', 'xlt', 'xlw', 'xml',
            'zip', '7z', 'xlsx','docx','svg']
    ];
    const UPLOAD_TYPE_COMMON = "common";
    const UPLOAD_TYPE_AVATAR = "avatar";
    const FILE_TYPE_IMAGE = 'image';
    const UPLOAD_LIMITS = [
        'image' => 1024 * 1024 * 10,
        'file' => 1024 * 1024 * 50
    ];

    private $configString;

    public function __construct($config = 'file',
                                OssFileModel $ossFileModel = null)
    {
        app()->configure('oss');
        $this->configString = $config;
        $config = config('oss.' . $config);
        $this->client = new OssClient(
            $config['access_key_id'],
            $config['access_key_secret'],
            $config['endpoint']
        );
        $this->bucket = $config['bucket'];
        $this->config = &$config;
        $this->model = $ossFileModel;
    }

    /**
     *
     * @param $object
     * @return string
     */
    public function getPublicUrl($object)
    {
        return $this->config['protocol'] . $this->config[$this->config['domain_use']] . '/' . $this->config['prefix'] . $object;
    }

    /**
     * 用户上传接口
     * @param int $userId 用户ID
     * @param string $ip ip地址
     * @param UploadedFile $file 用户文件
     * @param string $uploadType
     * @return array
     * @throws ApiException
     */
    public function userUpload($userId, $ip, UploadedFile $file, $uploadType)
    {
        $mimeType = $file->getMimeType();
        $size = $file->getSize();
        $path = $file->getPathname();
        $extension = $file->getClientOriginalExtension();
        $fileName = $file->getClientOriginalName();
        $upType = 0;
        if ($this::UPLOAD_TYPE_AVATAR == $uploadType)
            $upType = 1;
        $tmpPath = storage_path() . '/tmp';
        if (!file_exists($tmpPath)) {
            mkdir($tmpPath, 0777, true);
        }
        $newFileName = $userId . '_' . date('YmdHis') . '_' . mt_rand(100, 999) . '.' . $extension;
        $filePath = $tmpPath . '/' . $newFileName;
        $file->move($tmpPath, $newFileName);
        // 判断允许上传
        app()->configure('mime');
        $mime = config('mime');
        $realExtension = $mime[$mimeType] ?? strtolower($extension);
        $fileType = '';
        $fileTypeValue = 0;
        foreach (OssFileService::UPLOAD_ALLOWS as $k => $v) {
            $fileTypeValue++;
            if (in_array($realExtension, $v)) {
                $fileType = $k;
                break;
            }
        }
        if (empty($fileType) || empty($extension)) {
            @unlink($filePath);
            throw new ApiException(10001);
        }
        $limitSize = OssFileService::UPLOAD_LIMITS[$fileType];
        if ($size > $limitSize) {
            @unlink($filePath);
            throw new ApiException(10002, '文件过大', ['size' => round($limitSize / 1024 / 1024)]);
        }
        if (mb_strlen($fileName) >= 100) {
            @unlink($filePath);
            throw new ApiException('keyword.keyword_too_long');
        }
        $etag = md5_file($filePath);
        $size = filesize($filePath);
        // 检测文件是否已存在
        $uid = str_pad($userId, 10, '0', STR_PAD_LEFT);
        $ossFile = $this->model->findOneBy(['etag' => $etag, 'status' => 0], 'id,url,user_id');
        $self = $this;
        if ($ossFile) {
            @unlink($filePath);
            if ($ossFile['user_id'] == $userId) {
                return [
                    'id' => $ossFile['id'],
                    'name' => $file->getClientOriginalName(),
                    'object_key' => $ossFile['url'],
                    'url' => $this->getPublicUrl($ossFile['url'])
                ];
            } else {
                $ossFileUrl = $ossFile['url'];
                $ossFile = $this->model->findOneBy(['user_id' => $userId, 'etag' => $etag, 'status' => 0], 'id,url');
                if (!$ossFile) {
                    // 复制文件
                    $object = preg_replace("/^up\/[^\/]*/", 'up/' . $uid, $ossFileUrl);;
                    $response = $this->copyObject($ossFileUrl, $object);
                    $url = $response['info']['url'] ?? '';
                    if ($url) {
                        $ossFile = [];
                        $ossFile['user_id'] = $userId;
                        $ossFile['name'] = $fileName;
                        $ossFile['size'] = $size;
                        $ossFile['etag'] = $etag;
                        $ossFile['ip'] = $ip ? ip2long($ip) : 0;
                        $ossFile['file_type'] = $fileTypeValue;
                        $ossFile['up_type'] = $upType;
                        $ossFile['url'] = $object;
                        $ossFile['extension'] = $extension;
                        $ossFile['create_time'] = nowTime();
                        DB::transaction(function () use ($self, &$ossFile) {
                            $ossFileId = $this->model->insertData($ossFile);
                            if ($ossFileId > 0) {
                                $ossFile['id'] = $ossFileId;
                            }
                        });
                        return [
                            'id'=>$ossFile['id'],
                            'name'=>$file->getClientOriginalName(),
                            'object_key'=>$object,
                            'url'=>$this->getPublicUrl($object)
                        ];
                    } else {
                        throw new ApiException(10006);
                    }
                } else {
                    // 已上传过类似文件
                    return [
                        'id'=>$ossFile['id'],
                        'name'=>$file->getClientOriginalName(),
                        'object_key'=>$ossFile['url'],
                        'url'=>$this->getPublicUrl($ossFile['url'])
                    ];
                }
            }
        } else {
            $options = $this->getDefaultOptions($fileName, $extension);
            $object = 'up/'.$uid.'/'.date('Ymd').'/'.uniqid().'.'.$extension;
            $response = $this->uploadFile($object, $filePath, $options);
            $url = $response['info']['url'] ?? '';
            if ($url) {
                $ossFile = [];
                $ossFile['user_id'] = $userId;
                $ossFile['name'] = $fileName;
                $ossFile['size'] = $size;
                $ossFile['etag'] = $etag;
                $ossFile['file_type'] = $fileTypeValue;
                $ossFile['up_type'] = $upType;
                $ossFile['url'] = $object;
                $ossFile['ip'] = $ip ? ip2long($ip) : 0;
                $ossFile['extension'] = $extension;
                $ossFile['create_time'] = nowTime();

                DB::transaction(function () use ($self, &$ossFile) {
                    $ossFileId = $this->model->insertData($ossFile);
                    if ($ossFileId > 0) {
                        $ossFile['id'] = $ossFileId;
                    }
                });
                @unlink($filePath);
                return [
                    'id'=>$ossFile['id'],
                    'name'=>$file->getClientOriginalName(),
                    'object_key'=>$object,
                    'url'=>$this->getPublicUrl($object)
                ];
            } else {
                @unlink($filePath);
                Log::error('OSS上传不成功:' . json_encode($response));
                throw new ApiException(10007);
            }
        }
    }

    /**
     * 拷贝object
     * @param string $fromObject 源地址
     * @param string $toObject 目标地址
     * @return null
     */
    function copyObject($fromObject, $toObject)
    {
        try {
            return $this->client->copyObject($this->bucket,
                $this->config["prefix"] . $fromObject, $this->bucket,
                $this->config["prefix"] . $toObject);
        } catch (OssException $e) {
            Log::error("OSS拷贝出错:" . $e->getMessage());
            return null;
        }
    }

    /**
     * 获得默认选项
     * @param string $fileName 文件名
     * @param string $extension 扩展名
     * @return array options
     */
    private function getDefaultOptions($fileName, $extension)
    {
        $options = array(
            OssClient::OSS_HEADERS => array(
                'Cache-Control' => 'max-age=' . (86400 * 365),
                'Content-Disposition' => 'attachment;filename=' . urlencode($fileName),
                'x-oss-server-side-encryption' => 'AES256',
            ),
        );
        $headers_key = OssClient::OSS_HEADERS;
        if (in_array($extension, array('jpg', 'jpeg', 'gif', 'png'))) {
            $options[$headers_key]['Content-Disposition'] = "";
        } else if (in_array($extension, array('html', 'js', 'css', 'svg', 'woff', 'ttf', 'eot'))) {
            $options[$headers_key]['Content-Disposition'] = "";
            $options[$headers_key]['Cache-Control'] = 'max-age=86400';
        }
        return $options;
    }

    /**
     * 上传文件（File形式）
     * @param string $object 对象key
     * @param string $file 文件路径
     * @param array $options 选项
     * @return array
     */
    public function uploadFile($object, $file, $options = null)
    {
        try {
            return $this->client->uploadFile($this->bucket, $this->config["prefix"] . $object, $file, $options);
        } catch (OssException $e) {
            Log::error("OSS上传出错:" . $e->getMessage());
            return null;
        }
    }

    /**
     * 上传文件（content形式）
     * @param string $object 对象key
     * @param string $content 存储内容
     * @param array $options 选项
     * @return string
     */
    public function putObject($object, $content, $options = null)
    {
        try {
            return $this->client->putObject($this->bucket, $this->config["prefix"] . $object, $content, $options);
        } catch (OssException $e) {
            Log::error("OSS上传出错:" . $e->getMessage() . ">>" . $this->config["prefix"] . $object);
            return null;
        }
    }

    /**
     * 获取object的内容
     * @param OssClient $ossClient OSSClient实例
     * @param string $bucket 存储空间名称
     * @return null
     */
    public function getObject($object)
    {
        try {
            return $this->client->getObject($this->bucket, $this->config["prefix"] . $object);
        } catch (OssException $e) {
            Log::error("OSS获取出错:" . $e->getMessage() . ">>" . $this->config["prefix"] . $object);
            return null;
        }
    }

    /**
     * 保存文件到本地
     * */
    public function saveLocal($fileInfo){
        $tmpPath = storage_path() . '/tmp';
        $OldFilename = $fileInfo->getClientOriginalName();
        $extension = $fileInfo->getClientOriginalExtension();
        $fileSize = $fileInfo->getSize();
        $mimeType = $fileInfo->getMimeType();
        $newFileName =  date('YmdHis') . '_' . mt_rand(100, 999) . '.' . $extension;
        $fileInfo->move($tmpPath, $newFileName);
        return [
            'path' => $tmpPath . '/' . $newFileName,
            'size' => $fileSize,
            'filename' => $newFileName,
            'original_filename' => $OldFilename,
            'ext' => $extension,
            'mimeType' => $mimeType
        ];
    }

    /**
     * 版本包上传到oss
     * */
    public function uploadVersionPacket($fileInfo,$versions){
        $result = $this->saveLocal($fileInfo);
        $rand=mt_rand(1000, 9999);
        if (empty($versions)) {
            $versions = $rand;
        } else {
            $versions =  $rand.'/'.$versions;
        }
        if (in_array($result['ext'],['7z','zip'])) {
            $object='versions/'.$versions.'/app.'.$result['ext'];
            $ossResponse = $this->uploadFile($object, $result['path']);
            $ossUrl = $ossResponse['info']['url'] ?? '';
            if($ossUrl){
                @unlink($result['path']);
                $result['path']=$ossUrl;
            }
            return $result;
        } else{
            @unlink($result['path']);
            throw new ApiException('common.upload_packet_ext_error');
        }
    }

/**
* 安装包上传到oss
* */
    public function uploadInstallPacket($fileInfo){
        $result = $this->saveLocal($fileInfo);
        if (in_array($result['ext'], ['exe'])) {
            $object = 'install/'.$result['original_filename'];
            $ossResponse = $this->uploadFile($object, $result['path']);
            $ossUrl = $ossResponse['info']['url'] ?? '';
            if($ossUrl){
                @unlink($result['path']);
                $result['path']=$ossUrl;
            }
            return $result;
        } else{
            @unlink($result['path']);
            throw new ApiException('common.upload_packet_ext_error');
        }
    }
}
